import {
    request,
    summary,
    body,
    tags,
    middlewares,
    description,
    formData,
    responses,
    query,
    path as rpath
} from '../swagger';
import config from '../config';
import fileOperation from '../library/alioss/index';
import { info } from 'console';
import { wePush } from './weapp';
import { mobsfPush } from './mobsfapp';
import FormData from "form-data";

var Team = require('../model/team')
const Version = require('../model/version')
const App = require('../model/app_model')
var multer = require('koa-multer');
var fs = require('fs')
var crypto = require('crypto')
var path = require('path')
var os = require('os')
var mime = require('mime')
var uuidV4 = require('uuid/v4')
var apkParser3 = require('../library/apkparser/apkparser')
var unzip = require('unzipper')
var etl = require('etl')
var mkdirp = require('mkdirp')
var ipaMataData = require('ipa-metadata2')
const AppInfoParser = require('app-info-parser')
const { compose, maxBy, filter, get, F } = require('lodash/fp')

var { writeFile, readFile, responseWrapper, exec } = require('../helper/util')

var tempDir = path.join(config.fileDir, 'temp')
var uploadDir = path.join(config.fileDir, 'upload')

createFolderIfNeeded(tempDir)

var uploadPrefix = "upload";

function createFolderIfNeeded(path) {
    if (!fs.existsSync(path)) {
        mkdirp.sync(path, function (err) {
            if (err) console.error(err)
        })
    }
}

const storage = multer.diskStorage({
    destination: tempDir,
    filename: (req, file, cb) => cb(null, `${Date.now()}-${file.originalname}`)
});

const tag = tags(['上传']);
const upload = multer({ storage });

module.exports = class UploadRouter {
    @request('post', '/api/apps/{teamId}/upload')
    @summary('上传apk或者ipa文件到服务器')
    @tag
    @formData({
        file: {
            type: 'file',
            required: 'true',
            description: 'upload file, get url'
        }
    })
    @rpath({ teamId: { type: 'string', required: true } })
    @middlewares([upload.single('file')])
    static async upload(ctx, next) {
        var file = ctx.req.file
        const { release, branch, changelog } = ctx.request.query
        const { teamId } = ctx.validatedParams;
        var team = await Team.findById(teamId)
        if (!team) {
            throw new Error("没有找到该团队")
        }


        var result = await parseAppAndInsertToDB(file, ctx.state.user.data, team);
        const VersionSql = changelog ? { released: result.app.autoPublish, changelog } : { released: result.app.autoPublish }
        await Version.updateOne({ _id: result.version._id }, VersionSql)
        console.log('release,', release)
        console.log('branch=>', branch)
        console.log('changelog=>', changelog)
        if (result.app.autoPublish || release == 'True') {
            await App.updateOne({ _id: result.app._id }, {
                releaseVersionId: result.version._id,
                releaseVersionCode: result.version.versionCode
            })
            console.log('start wechat push')
            if (changelog) {
                result.version.changelog = changelog
            }
            wePush(result.app, result.version, branch)
        }
        console.log('result.app.autoPublish=>,', result.app.autoPublish)

        try {
            const sy = path.extname(file.path)
            if (".apk,.ipa".includes(sy)) {
                mobsf(file.path, file.filename, result.app, result.version, branch)
            }
        } catch (error) {
            console.log(error)
        }
        ctx.body = responseWrapper(result);
    }

    static async download(ctx, next) {
        console.log(body)
    }
}

/**
 *
 * @param 时间戳
 * @returns yyyy-mm-dd
 */
/**
 *
 * @param 时间戳
 * @returns 06181503 格式
 */
function getTime(t) {
    const date_now = new Date(t);
    const nowDate = [];
    const nowTime = [];
    // const year = date_now.getFullYear();
    const month = date_now.getMonth() + 1 < 10 ? `0${date_now.getMonth() + 1}` : (date_now.getMonth() + 1);
    const date = date_now.getDate() < 10 ? `0${date_now.getDate()}` : date_now.getDate();
    const hours = date_now.getHours() < 10 ? `0${date_now.getHours()}` : (date_now.getHours());
    const minutes = date_now.getMinutes() < 10 ? `0${date_now.getMinutes()}` : (date_now.getMinutes());
    const millis = date_now.getSeconds() < 10 ? `0${date_now.getSeconds()}` : (date_now.getSeconds());
    // nowDate.push(year);
    nowDate.push(month);
    nowDate.push(date);
    nowTime.push(hours);
    nowTime.push(minutes);
    nowTime.push(millis);
    const time = `${nowDate.join('')}${nowTime.join('')}`;
    return time;
}

const kehuduan = async (file, user, team) => {
    let fpUrl = file.path
    let info = {}
    info.platform = 'client'
    info.versionCode = 0
    info.appName = '一派客户端'
    info.bundleId = 'com.daofeng.peiwan'
    let fileName = file.originalname.split('dofun')[0]
    const versionTime = getTime(Date.now());
    info.versionStr = `${fileName}_${versionTime}`
    // console.log(file)
    info.ownerId = team._id
    var buffer = fs.readFileSync(fpUrl)
    const url = await fileOperation.putFile(`${info.versionStr}${path.extname(fpUrl)}`, buffer)
    var fsHash = crypto.createHash('md5')
    fsHash.update(buffer)
    var filemd5 = fsHash.digest('hex')
    info.downloadUrl = url
    var app = await App.findOne({ 'platform': info['platform'], 'bundleId': info['bundleId'], 'ownerId': team._id })
    if (!app) {
        info.creator = user.username;
        info.creatorId = user._id;
        info.shortUrl = Math.random().toString(36).substring(2, 5) + Math.random().toString(36).substring(2, 5);
        console.log(info)
        app = new App(info)
        app.ownerId = team._id;
        app.currentVersion = versionTime
        app.iconBase64 = ``
        await app.save()
        info.uploader = user.username;
        info.uploaderId = user._id;
        info.size = buffer.length
        info.versionStr = `${fileName}_${versionTime}`
        info.iconBase64 = app.iconBase64
        var version = Version(info)
        version.md5 = filemd5
        version.appId = app._id;
        version.installUrl = info.downloadUrl
        await version.save()
        return { 'app': app, 'version': version }
    }
    var version = await Version.findOne({ appId: app.id, versionStr: info.versionStr })
    if (!version) {
        info.uploader = user.username;
        info.uploaderId = user._id;
        info.size = buffer.length
        var version = Version(info)
        version.appId = app._id;
        version.md5 = filemd5
        version.iconBase64 = app.iconBase64
        version.versionStr = `${fileName}_${versionTime}`
        version.installUrl = info.downloadUrl
        await version.save()
        return { 'app': app, 'version': version }
    } else {
        let err = Error()
        err.code = 408
        err.message = '当前版本已存在'
        throw err
    }
}

async function parseAppAndInsertToDB(file, user, team) {
    var filePath = file.path
    const versionTime = getTime(Date.now());
    var parser, extractor;
    if (path.extname(filePath) === ".ipa") {
        parser = parseIpa
        extractor = extractIpaIcon
    } else if (path.extname(filePath) === ".apk") {
        parser = parseApk
        extractor = extractApkIcon
    } else if (path.extname(filePath) === ".exe" && filePath.endsWith("dofun.exe")) {
        return await kehuduan(file, user, team)
    } else {
        throw (new Error("文件类型有误,仅支持IPA或者APK文件的上传."))
    }

    //解析ipa和apk文件
    var info = await parser(filePath);

    var fileName = info.bundleId + "_" + info.versionStr + "_" + info.versionCode + "_" + versionTime


    //解析icon图标
    // var icon = await extractor(filePath, fileName, team);
    //移动文件到对应目录
    // var fileRelatePath = path.join(team.id, info.platform)
    // createFolderIfNeeded(path.join(uploadDir, fileRelatePath))
    // var fileRealPath = path.join(uploadDir, fileRelatePath, fileName + path.extname(filePath))

    //获取文件MD5值
    var buffer = fs.readFileSync(filePath)

    const url = await fileOperation.putFile(`${fileName}${path.extname(filePath)}`, buffer)
    var fsHash = crypto.createHash('md5')
    fsHash.update(buffer)
    var filemd5 = fsHash.digest('hex')

    //异步保存问题（避免跨磁盘移动问题）
    // var readStream = fs.createReadStream(filePath)
    // var writeStream = fs.createWriteStream(fileRealPath)
    // readStream.pipe(writeStream)
    // readStream.on('end',function(){
    //     fs.unlinkSync(filePath)
    // })

    // info.downloadUrl = path.join(uploadPrefix, fileRelatePath, fileName + path.extname(filePath))
    info.downloadUrl = url
    var app = await App.findOne({ 'platform': info['platform'], 'bundleId': info['bundleId'], 'ownerId': team._id })
    if (!app) {
        info.creator = user.username;
        info.creatorId = user._id;
        // info.icon = path.join(uploadPrefix, icon.fileName);
        info.shortUrl = Math.random().toString(36).substring(2, 5) + Math.random().toString(36).substring(2, 5);
        app = new App(info)
        app.ownerId = team._id;
        app.currentVersion = info.versionCode
        app.iconBase64 = info.iconBase64

        await app.save()
        info.uploader = user.username;
        info.uploaderId = user._id;
        info.size = buffer.length
        // info.size = fs.statSync(filePath).size
        info.versionStr = `${info.versionStr}_${versionTime}`
        var version = Version(info)
        version.md5 = filemd5
        version.appId = app._id;
        if (app.platform == 'ios') {
            version.installUrl = mapInstallUrl(app.id, version.id)
        } else {
            version.installUrl = info.downloadUrl
        }
        // console.log('@info',info)
        // console.log('@fileRealPath',fileRealPath)
        await version.save()
        return { 'app': app, 'version': version }
    }
    var version = await Version.findOne({ appId: app.id, versionStr: info.versionStr })
    if (!version) {
        info.uploader = user.username;
        info.uploaderId = user._id;
        info.size = buffer.length
        var version = Version(info)
        version.appId = app._id;
        version.md5 = filemd5
        version.versionStr = `${info.versionStr}_${versionTime}`
        if (app.platform == 'ios') {
            version.installUrl = mapInstallUrl(app.id, version.id)
        } else {
            version.installUrl = info.downloadUrl
        }
        await version.save()
        // console.log('@info',info)
        // console.log('@app',app)
        // console.log('@version',version)
        return { 'app': app, 'version': version }
    } else {

        // info.uploader = user.username;
        // info.uploaderId = user._id;
        // info.size = fs.statSync(fileRealPath).size
        // var version = Version(info)
        // version.versionStr = `${version.versionStr}_${getTime(Date.now())}`
        // version.appId = app._id;
        // version.md5 = filemd5
        // if (app.platform == 'ios') {
        //     version.installUrl = mapInstallUrl(app.id, version.id)
        // } else {
        //     version.installUrl = `${config.baseUrl}/${info.downloadUrl}`
        // }
        // await version.save()

        // console.log('@info',info)
        // console.log('@app',app)
        // console.log('@version',version)
        // return { 'app': app, 'version': version }
        let err = Error()
        err.code = 408
        err.message = '当前版本已存在'
        throw err
    }






}

///映射可安装的app下载地址
function mapInstallUrl(appId, versionId) {
    return `itms-services://?action=download-manifest&url=${config.baseUrl}/api/plist/${appId}/${versionId}`
}

///移动相关信息到指定目录
function storeInfo(filename, guid) {
    var new_path
    if (path.extname(filename) === '.ipa') {
        new_path = path.join(ipasDir, guid + '.ipa')
    } else if (path.extname(filename) === '.apk') {
        new_path = path.join(apksDir, guid + '.apk')
    }
    fs.rename(filename, new_path)
}

///解析ipa
function parseIpa(filename) {
    const parser = new AppInfoParser(filename)

    return new Promise((resolve, reject) => {
        parser.parse().then(result => {
            console.log('app info ----> ', result)
            console.log('icon base64 ----> ', result.icon)

            var info = {}
            info.platform = 'ios'
            info.bundleId = result.CFBundleIdentifier
            info.bundleName = result.CFBundleName
            info.appName = result.CFBundleDisplayName
            info.versionStr = result.CFBundleShortVersionString
            info.versionCode = result.CFBundleVersion
            info.iconName = result.CFBundleIcons.CFBundlePrimaryIcon.CFBundleIconName
            info.iconBase64 = result.icon
            try {
                const environment = result.mobileProvision.Entitlements['aps-environment']
                const active = result.mobileProvision.Entitlements['beta-reports-active']
                if (environment == 'production') {
                    info.appLevel = active ? 'appstore' : 'enterprise'
                } else {
                    info.appLevel = 'develop'
                }
            } catch (err) {
                info.appLevel = 'develop'
                // reject("应用未签名,暂不支持")
            }
            resolve(info)
        })

    })
}

///解析ipa icon
async function extractIpaIcon(filename, guid, team) {
    var ipaInfo = await parseIpa(filename)
    var iconName = ipaInfo.iconName || 'AppIcon';
    var tmpOut = tempDir + '/{0}.png'.format(guid)
    var found = false
    var buffer = fs.readFileSync(filename)
    var data = await unzip.Open.buffer(buffer)
    await new Promise((resolve, reject) => {
        data.files.forEach(file => {
            if (file.path.indexOf(iconName + '60x60@2x.png') != -1) {
                found = true
                file.stream()
                    .pipe(fs.createWriteStream(tmpOut))
                    .on('error', reject)
                    .on('finish', resolve)
            }
        })
    }).catch({

    })

    if (!found) {
        throw (new Error('can not find icon'))
    }

    var pnfdefryDir = path.join(__dirname, '..', 'library/pngdefry')
    //写入成功判断icon是否是被苹果破坏过的图片
    var exeName = '';
    if (os.type() === 'Darwin') {
        exeName = 'pngfy-osx';
    } else if (os.type() === 'Linux') {
        exeName = 'pngfy-linux';
    } else {
        throw new Error('Unknown OS!');
    }

    var { stderr, stdout } = await exec(path.join(pnfdefryDir, exeName + ' -s _tmp ', tmpOut));
    if (stderr) {
        throw stderr;
    }
    //执行pngdefry -s xxxx.png 如果结果显示"not an -iphone crushed PNG file"表示改png不需要修复
    var iconRelatePath = path.join(team.id, "/icon")
    var iconSuffix = "/" + guid + "_i.png"
    createFolderIfNeeded(path.join(uploadDir, iconRelatePath))
    if (stdout.indexOf('not an -iphone crushed PNG file') != -1) {
        await fs.renameSync(tmpOut, path.join(uploadDir, iconRelatePath, iconSuffix))
        return { 'success': true, 'fileName': iconRelatePath + iconSuffix }
    }
    await fs.unlinkSync(tmpOut)
    fs.renameSync(tempDir + '/{0}_tmp.png'.format(guid), path.join(uploadDir, iconRelatePath, iconSuffix))
    return { 'success': true, 'fileName': iconRelatePath + iconSuffix }

}

///解析apk
function parseApk(filename) {

    const parser = new AppInfoParser(filename)


    return new Promise((resolve, reject) => {
        parser.parse().then(result => {
            // console.log('app info ----> ', result)
            // console.log('icon base64 ----> ', result.icon)
            // console.log('====================================', JSON.stringify(result));
            var label = undefined

            if (result.application && result.application.label && result.application.label.length > 0) {
                label = result.application.label[0]
            }

            if (label) {
                label = label.replace(/'/g, '')
            }
            var appName = (result['application-label'] || result['application-label-zh-CN'] || result['application-label-es-US'] ||
                result['application-label-zh_CN'] || result['application-label-es_US'] || label || 'unknown')
            var info = {
                'appName': appName.replace(/'/g, ''),
                'versionCode': Number(result.versionCode),
                'bundleId': result.package,
                'versionStr': result.versionName,
                'platform': 'android',
                'iconBase64': result.icon
            }
            // console.log("@info",info)
            resolve(info)
        }).catch(err => {
            console.log('err ----> ', err)
        })
    })
}

///解析apk icon
function extractApkIcon(filepath, guid, team) {
    return new Promise((resolve, reject) => {
        apkParser3(filepath, (err, data) => {
            var iconPath = false;
            var iconSize = [640, 320, 240, 160]
            // var iconName = ''
            // console.log(data)
            for (var i in iconSize) {
                if (typeof data['application-icon-' + iconSize[i]] !== 'undefined') {
                    iconPath = data['application-icon-' + iconSize[i]]
                    // iconName =  data['application-icon-' + iconSize[i]]
                    break;
                }
            }
            if (!iconPath) {
                throw ('can not find app icon')
            }

            iconPath = iconPath.replace(/'/g, '')
            var dir = path.join(uploadDir, team.id, "icon")
            var realPath = path.join(team.id, "icon", '/{0}_a.png'.format(guid))
            createFolderIfNeeded(dir)
            var tempOut = path.join(uploadDir, realPath)

            var { ext, dir } = path.parse(iconPath);
            // 获取到最大的png的路径
            let maxSizePath;

            const initialPromise = ext === '.xml' ?
                unzip.Open.file(filepath).then(directory => {
                    const getMaxSizeImagePath = compose(get('path'), maxBy('compressedSize'),
                        filter(entry => entry.path.indexOf(dir) >= 0 && entry.path.indexOf('.png') >= 0), get('files'));
                    maxSizePath = getMaxSizeImagePath(directory)
                }) : new Promise((resolve) => resolve())
            initialPromise.then(() => {
                fs.createReadStream(filepath)
                    .pipe(unzip.Parse())
                    .pipe(etl.map(entry => {
                        // 适配iconPath为ic_launcher.xml的情况
                        const entryPath = entry.path
                        // console.log("@entry.path-------",entry.path)
                        const isXml = entryPath.indexOf(".xml") >= 0
                        // if( iconName.indexOf(entryPath) >= 0){
                        //     entry.pipe(etl.toFile(tempOut))
                        //     resolve({ 'success': true, fileName: realPath })
                        // }
                        if ((!isXml && entryPath.indexOf(iconPath) != -1) || (isXml && entry.path.indexOf(maxSizePath) != -1)) {
                            entry.pipe(etl.toFile(tempOut))
                            resolve({ 'success': true, fileName: realPath })
                        } else {
                            resolve({ 'success': true, fileName: realPath })
                            entry.autodrain()
                        }
                    }))
            })
        })
    })
}

///格式化输入字符串 /用法: "node{0}".format('.js'), 返回'node.js'
String.prototype.format = function () {
    var args = arguments
    return this.replace(/\{(\d+)\}/g, function (s, i) {
        return args[i]
    })
}

function parseText(text) {
    var regx = /(\w+)='([\S]+)'/g
    var match = null;
    var result = {}
    while (match = regx.exec(text)) {
        result[match[1]] = match[2]
    }
    return result
}

// mobsfPush = (app, version, branch, filename) 
const mobsf = async (path, filename, app, version, branch) => {
    const fd = new FormData()
    fd.append('file', fs.createReadStream(path), {
        filename: filename,
        filepath: path,
        contentType: filename.includes('.apk') ? 'application/vnd.android.package-archive' : 'application/octet-stream',
    });
    console.log('mobsf push ing ... ');
    fd.submit({ host: config.mobsfLocatHost, port: config.mobsfLocatPort, path: '/api/v1/upload', headers: { Authorization: config.mobsfAuth } }, async (err, res) => {
        if (err) throw err;
        console.log('mobsf push Done');
        await mobsfPush(app, version, branch, filename)
        // const scanType = app.platform == 'android' ? 'apk' : 'ipa'
        // const result = await mobsfScan(scanType, filename, version.md5, 1)
        // console.log(result)
    });
}